# -*- coding: utf-8 -*-

from functools import partial
from decimal import Decimal

from common.lottery.cyclical.ff_ssc.model.order import BET_TYPE
from common.utils import math_helper
from common.lottery.cyclical.ssc.order import (
    ssc_calc_win_price_decorator,
    ssc_calc_total_price_decorator,
    ssc_valid_number_decorator,
    ssc_generate_win_func_decorator
)

RATE_CONFIG = {  # base on UNIT==2
    BET_TYPE.ONE_SINGLE: 10,
    BET_TYPE.TWO_SINGLE: 100,
    BET_TYPE.THREE_SINGLE: 1000,
    BET_TYPE.FIVE_SINGLE: 100000,
    BET_TYPE.TWO_COMBINE: 50,
    BET_TYPE.THREE_GROUP_SINGLE: 320,
    BET_TYPE.THREE_GROUP_MULTI: 320,
    BET_TYPE.THREE_GROUP_SIX: 160,
    BET_TYPE.FIVE_ALL: [20000, 200, 20],  # 全部，前后三，前后二
    BET_TYPE.TWO_BSOE: 4,
    BET_TYPE.TWO_SUM: 100,
    BET_TYPE.CHOSEN_ONE: 10,
}

UNIT = 2  # 彩票单价


@ssc_valid_number_decorator
def valid_number(bet_type, number):
    '''同一位之间用','隔开
       不同位之间用'|'隔开
       与顺序无关的选项，使用','隔开
    '''
    positions = number.split('|')
    nums = []
    for position in positions:
        if not position:
            continue
        ds = position.split(',')
        for d in ds:
            if bet_type == BET_TYPE.TWO_SUM:
                assert 0 <= int(d) <= 18, 'two sum name range error'
            elif bet_type == BET_TYPE.TWO_BSOE:
                # big: 大, small: 小, odd: 单, even: 双
                assert d in ('b', 's', 'o', 'e'), 'number format wrong'
            else:
                assert 0 <= int(d) <= 9, 'number range error'
        nums.append(ds)
    if bet_type == BET_TYPE.TWO_BSOE:
        assert len(positions) == 2, 'bsoe number format error'
    elif bet_type == BET_TYPE.ONE_SINGLE:
        assert len(positions) == 1 and len(nums) == 1, \
            'one single number format error'
        assert 1 <= len(nums[0]) <= 10, 'one single number format error'
    elif bet_type == BET_TYPE.TWO_SINGLE:
        assert len(positions) == 2, 'two single number format error'
    elif bet_type == BET_TYPE.THREE_SINGLE:
        assert len(positions) == 3, 'three single number format error'
    elif bet_type == BET_TYPE.FIVE_SINGLE:
        assert len(positions) == 5, 'five single number format error'
    elif bet_type == BET_TYPE.TWO_COMBINE:
        assert len(positions) == 1 and len(nums) == 1, \
            'two combine number format error'
        assert len(set(nums[0])) == len(nums[0]), 'two combine number no dup'
    elif bet_type == BET_TYPE.THREE_GROUP_SINGLE:
        assert len(positions) == 1 and len(nums) == 1, \
            'three group single number format error'
        assert len(nums[0]) == 3, \
            'three group single number length should be 3'
        assert len(set(nums[0])) == 2, 'three group single should has 2 equal'
    elif bet_type == BET_TYPE.THREE_GROUP_MULTI:
        assert len(positions) == 1 and len(nums) == 1, \
            'three group multi number format error'
        assert len(nums[0]) >= 2, 'three group multi number should at least 2'
        assert len(set(nums[0])) == len(nums[0]), \
            'three group multi number dup'
    elif bet_type == BET_TYPE.THREE_GROUP_SIX:
        assert len(positions) == 1 and len(nums) == 1, \
            'three group six number format error'
        assert len(nums[0]) >= 3, 'three group six number at least 3'
        assert len(set(nums[0])) == len(nums[0]), 'three group six number dup'
    elif bet_type == BET_TYPE.FIVE_ALL:
        assert len(positions) == 5, 'five all number format wrong'
    elif bet_type == BET_TYPE.TWO_SUM:
        assert len(positions) == 1 and len(nums) == 1, \
            'two sum number need no `|`'
        assert 19 >= len(nums[0]) >= 1, 'two sum number format wrong'
    elif bet_type == BET_TYPE.CHOSEN_ONE:
        assert len(positions) == 5 and len(nums) >= 1, 'format wrong'
    else:
        assert False, 'bet_type invalid'


@ssc_calc_total_price_decorator
def calc_total_price(bet_type, number, times=1, unit=UNIT):
    '''根据不同的竞猜方式计算所需总价
    '''
    positions = number.split('|')
    nums = []
    for position in positions:
        if not position:
            continue
        ds = position.split(',')
        nums.append(ds)
    if bet_type in (BET_TYPE.ONE_SINGLE, BET_TYPE.TWO_SINGLE,
                    BET_TYPE.THREE_SINGLE, BET_TYPE.FIVE_SINGLE,
                    BET_TYPE.FIVE_ALL,
                    BET_TYPE.TWO_BSOE,
                    BET_TYPE.CHOSEN_ONE):
        # 这几种都是用逗号直接分割，总注数等于各串长度之乘积
        price = 0 if bet_type == BET_TYPE.CHOSEN_ONE else 1
        for num in nums:
            if bet_type == BET_TYPE.CHOSEN_ONE:  # 定位胆的注数是和值
                price += len(num)
            else:
                price *= len(num)
        return price * times * unit
    elif bet_type == BET_TYPE.TWO_COMBINE:
        # 两星组选，一个字符串，个位十位所有数字的组合，结果为C(n, 2)
        n = len(nums[0])
        return math_helper.c(n, 2) * times * unit
    elif bet_type == BET_TYPE.THREE_GROUP_SIX:
        # 组选六，类似，也是一个组合
        n = len(nums[0])
        return math_helper.c(n, 3) * times * unit
    elif bet_type == BET_TYPE.THREE_GROUP_SINGLE:
        # 组三单选，固定是一注
        return times * unit
    elif bet_type == BET_TYPE.THREE_GROUP_MULTI:
        # 组三复式，先任选两个，其中有一个要出现两次，所以是C(n, 2) * 2 = A(n, 2)
        n = len(nums[0])
        return math_helper.a(n, 2) * times * unit
    elif bet_type == BET_TYPE.TWO_SUM:
        # 二星和值，这个对于每个数字，出现的概率是固定的
        count = 0
        for num in nums[0]:
            n = int(num)
            if n < 10:
                count += (n + 1)
            else:
                count += (19 - n)
        return count * times * unit
    else:
        assert False, 'bet_type %s invalid' % bet_type


@ssc_generate_win_func_decorator
def generate_win_func(number):
    '''根据开奖结果生成根据押注方式对应的中奖函数，避免重复计算
    '''
    result = {}

    def n_single(n, x):
        positions = x.split('|')
        is_in = True
        for i, v in enumerate(number[-n:]):
            if v not in positions[i]:
                is_in = False
                break
        return 1 if is_in else 0

    result[BET_TYPE.ONE_SINGLE] = partial(n_single, 1)
    result[BET_TYPE.TWO_SINGLE] = partial(n_single, 2)
    result[BET_TYPE.THREE_SINGLE] = partial(n_single, 3)
    result[BET_TYPE.FIVE_SINGLE] = partial(n_single, 5)

    def chosen_one(x):
        xs = x.split('|')
        r = 0
        for i, v in enumerate(xs):
            hit = number[i]
            if hit in v:
                r += 1
        return r

    result[BET_TYPE.CHOSEN_ONE] = chosen_one

    def n_combine(n, x):
        '''如果有重复数字视为失败
        '''
        nums = x.split(',')
        return 1 if len(set(nums) & set(number[-n:])) == n else 0

    result[BET_TYPE.TWO_COMBINE] = partial(n_combine, 2)
    result[BET_TYPE.THREE_GROUP_SIX] = partial(n_combine, 3)

    last_3_set = set(number[-3:])
    is_three_group = len(last_3_set) == 2

    def three_group_single(x):
        x = ''.join(x.split(','))
        if not is_three_group:
            return 0
        if sorted(number[-3:]) == sorted(x):
            return 1
        return 0

    def three_group_multi(x):
        nums = x.split(',')
        if not is_three_group:
            return 0
        is_all_in = True
        for k in last_3_set:
            if k not in nums:
                is_all_in = False
                break
        return 1 if is_all_in else 0

    result[BET_TYPE.THREE_GROUP_SINGLE] = three_group_single
    result[BET_TYPE.THREE_GROUP_MULTI] = three_group_multi

    def five_all(x):
        '''和其他不同，这里返回的是命中的序列，即全中、前后三、前后二,
           没中的话，返回-1
        '''
        positions = x.split('|')
        max_hits = 0
        max_start = -1
        hit_start = -1
        hits = 0
        for i, position in enumerate(positions):
            nums = position.split(',')
            if number[i] in nums:
                hits += 1
                if hit_start == -1:
                    hit_start = i
            else:
                if hits > max_hits:
                    max_hits = hits
                    max_start = hit_start
                hit_start = -1
                hits = 0
        if hits > max_hits:
            max_hits = hits
            max_start = hit_start

        if max_hits == 5:
            return 0
        if max_hits >= 3 and max_start != 1:
            return 1
        if max_hits == 2 and (max_start == 0 or max_start == 3):
            return 2
        return -1

    result[BET_TYPE.FIVE_ALL] = five_all

    def two_bsoe(x):
        last_2 = number[-2:]
        desc = [set(), set()]
        for i, letter in enumerate(last_2):
            n = int(letter)
            if n >= 5:
                desc[i].add('b')
            else:
                desc[i].add('s')
            if n % 2 == 0:
                desc[i].add('e')
            else:
                desc[i].add('o')

        guesses = x.split('|')
        hit = 1
        for i, guess in enumerate(guesses):
            t = guess.split(',')
            hit *= len(set(t) & desc[i])
        return hit

    result[BET_TYPE.TWO_BSOE] = two_bsoe

    def two_sum(x):
        guesses = x.split(',')
        real_sum = str(int(number[-1]) + int(number[-2]))
        return 1 if real_sum in guesses else 0

    result[BET_TYPE.TWO_SUM] = two_sum

    return result


@ssc_calc_win_price_decorator
def calc_win_price(funcs, bet_number, bet_type, times=1, unit=UNIT,
                   rate_config=RATE_CONFIG):
    '''根据开奖结果和押注方式/倍数，计算中奖金额
    '''
    func = funcs.get(bet_type)
    base = 0
    if bet_type == BET_TYPE.FIVE_ALL:
        index = func(bet_number)
        if index >= 0:
            base = Decimal(rate_config.get(bet_type)[index])
    else:
        base = func(bet_number) * Decimal(rate_config.get(bet_type))

    return Decimal(base) * Decimal(times) * Decimal(unit) / Decimal(UNIT)
